package com.hzw.saas.service.old.hub.service.impl;

import java.io.*;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.StandardCopyOption;

import com.hzw.saas.common.util.enums.ConflictModeEnum;
import com.hzw.saas.service.old.hub.constants.IoConsts;
import com.hzw.saas.service.old.hub.enums.PathType;
import com.hzw.saas.service.old.hub.pojo.FileStorage;
import com.hzw.saas.service.old.hub.service.IFileStorageService;
import com.hzw.saas.service.old.hub.util.StorageUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 文件存储默认实现
 *
 * @author zzl
 * @since 04/20/2021
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class OldFileStorageServiceImpl implements IFileStorageService {

    @Override
    public File getFile(String fileName, String... superPaths) {
        Assert.isTrue(StrUtil.isNotBlank(fileName), "fileId is blank");
        String superPath = StorageUtil.connectPath(superPaths);
        String filePath = StorageUtil.connectPath(superPath, fileName);
        return FileUtil.file(filePath);
    }

    @Override
    public boolean existsFile(String fileName, String... superPaths) {
        File file = this.getFile(fileName, superPaths);
        return FileUtil.isFile(file);
    }

    @Override
    public long getFileLength(String fileName, String... superPaths) {
        File file = this.getFile(fileName, superPaths);
        return StorageUtil.getFileLength(file);
    }

    @Override
    public String getFileDigest(String digestType, String fileName, String... superPaths) {
        File file = this.getFile(fileName, superPaths);
        return StorageUtil.getFileDigest(file, digestType);
    }

    @Override
    public File mkFolder(String path, String... superPaths) throws IORuntimeException {
        File folder = this.getFile(path, superPaths);
        return this.mkFolder(folder);
    }

    @Override
    public File mkFolder(File folder) {
        log.debug("create folder: {}", folder);
        if (!StorageUtil.isAllDir(FileUtil.getAbsolutePath(folder)))
            throw new IORuntimeException(new FileAlreadyExistsException("创建目录失败，存在同名文件: " + folder));
        folder = FileUtil.mkdir(folder);
        log.info("create folder completed: {}", folder);
        return folder;
    }

    @Override
    public File saveFile(FileStorage fileInfo, InputStream in, ConflictModeEnum mode) throws IORuntimeException {
        // 参数校验
        Assert.notNull(in, "save file: input stream is null.");
        Assert.notNull(fileInfo, "save file: fileInfo is null");
        // 目标文件
        File file = this.getFile(fileInfo.getFileName(), fileInfo.getSuperPath());
        log.debug("save the file by input stream: {}", file);
        // 重名检查
        file = StorageUtil.doConflictName(file, mode, PathType.FILE, true);
        fileInfo.setFileName(FileUtil.getName(file));
        // 保存
        long costTime = this.saveFile(in, file, fileInfo.length());
        // 校验目标文件
        StorageUtil.checkTarLength(file, fileInfo.getLength(), true);
        StorageUtil.checkTarDigest(file, fileInfo.getDigest(), fileInfo.getDigestType(), true);
        log.info("save the file({}) completed, cost {}ms", file, costTime);
        return file;
    }

    @Override
    public File saveFile(FileStorage fileInfo, File inFile, ConflictModeEnum mode) throws IORuntimeException {
        // 校验参数
        Assert.isTrue(inFile.isFile(), "save file: source file is not exist.");
        Assert.notNull(fileInfo, "save file: fileInfo is null");
        // 校验源
        StorageUtil.exists(inFile, true);
        StorageUtil.checkSrcLength(inFile, fileInfo.getLength());
        StorageUtil.checkSrcDigest(inFile, fileInfo.getDigest(), fileInfo.getDigestType());
        // 目标文件
        File outFile = this.getFile(fileInfo.getFileName(), fileInfo.getSuperPath());
        log.debug("save the file by source file: {}", outFile);
        // 重名检查
        outFile = StorageUtil.doConflictName(outFile, mode, PathType.FILE, true);
        fileInfo.setFileName(FileUtil.getName(outFile));
        long costTime = this.copyFile(inFile, outFile);
        // 校验目标
        StorageUtil.checkTarLength(outFile, fileInfo.getLength(), true);
        StorageUtil.checkTarDigest(outFile, fileInfo.getDigest(), fileInfo.getDigestType(), true);
        log.info("save the file({}) completed, cost {}ms", outFile, costTime);
        return outFile;
    }

    @Override
    public File saveFile(FileStorage fileInfo, byte[] data, ConflictModeEnum mode) throws IORuntimeException {
        // 参数校验
        Assert.notNull(data, "save file: data to save is null.");
        Assert.notNull(fileInfo, "save file: file info is null");
        // 校验源
        StorageUtil.checkSrcLength(data, fileInfo.getLength());
        StorageUtil.checkSrcDigest(data, fileInfo.getDigest(), fileInfo.getDigestType());
        // 目标文件
        File file = this.getFile(fileInfo.getFileName(), fileInfo.getSuperPath());
        log.debug("save the file by bytes: {}", file);
        // 重名检查
        file = StorageUtil.doConflictName(file, mode, PathType.FILE, true);
        fileInfo.setFileName(file.getName());
        // 保存
        long costTime = this.saveFile(data, file);
        // 校验目标
        StorageUtil.checkTarLength(file, fileInfo.getLength(), true);
        StorageUtil.checkTarDigest(file, fileInfo.getDigest(), fileInfo.getDigestType(), true);
        log.info("save the file({}) completed, cost {}ms", file, costTime);
        return file;
    }

    @Override
    public File renameFile(String superPath, String srcName, String tarName, ConflictModeEnum mode) throws IORuntimeException {
        File oldFile = this.getFile(srcName, superPath);
        return this.renameFile(oldFile, tarName, mode);
    }

    @Override
    public File renameFile(File srcFile, String tarName, ConflictModeEnum mode) throws IORuntimeException {
        File newFile = FileUtil.file(srcFile.getParentFile(), tarName);
        log.debug("rename file in path ({}): {} -> {}", srcFile.getParent(), srcFile.getName(), tarName);
        // check source exists
        StorageUtil.exists(srcFile, true);
        // check unchanged name
        if (FileUtil.equals(srcFile, newFile)) {
            log.debug("rename file cancel, name unchanged: {}", srcFile);
            return srcFile;
        }
        // deal with target conflict
        PathType pathType = PathType.instance(srcFile);
        newFile = StorageUtil.doConflictName(newFile, mode, pathType, true);
        this.renameFile(srcFile, newFile.getName());
        log.info("rename file in path ({}) completed: {} -> {}", srcFile.getParent(), srcFile.getName(), tarName);
        return newFile;
    }

    @Override
    public File copyFile(String srcSuperPath, String srcName, String tarSuperPath, String tarName, ConflictModeEnum mode) throws IORuntimeException {
        File srcFile = this.getFile(srcName, srcSuperPath);
        File tarFile = this.getFile(tarName, srcSuperPath);
        tarFile = this.copyFile(srcFile, tarFile, mode);
        return tarFile;
    }

    @Override
    public File copyFile(File srcFile, File tarFile, ConflictModeEnum mode) throws IORuntimeException {
        log.debug("copy the file: {} -> {}", srcFile, tarFile);
        // check source exists
        StorageUtil.exists(srcFile, true);
        // check copy in same dir
        if (FileUtil.equals(srcFile, tarFile)) {
            log.debug("copy file in same dir: {}", srcFile);
            mode = ConflictModeEnum.RENAME_SRC;
        }
        // deal with target conflict
        PathType pathType = PathType.instance(srcFile);
        tarFile = StorageUtil.doConflictName(tarFile, mode, pathType, true);
        long costTime = this.copyFile(srcFile, tarFile);
        log.info("copy the file ({} -> {}) completed, cost {}ms", srcFile, tarFile, costTime);
        return tarFile;
    }

    @Override
    public File moveFile(String srcSuperPath, String srcName, String tarSuperPath, String tarName, ConflictModeEnum mode) throws IORuntimeException {
        File srcFile = this.getFile(srcName, srcSuperPath);
        File tarFile = this.getFile(tarName, tarSuperPath);
        tarFile = this.moveFile(srcFile, tarFile, mode);
        return tarFile;
    }

    @Override
    public File moveFile(File srcFile, File tarFile, ConflictModeEnum mode) throws IORuntimeException {
        log.debug("move the file: {} -> {}", srcFile, tarFile);
        // check source exists
        StorageUtil.exists(srcFile, true);
        // check move in same dir
        if (FileUtil.equals(srcFile, tarFile)) {
            log.debug("move file cancel, same target dir: {}", srcFile);
            return srcFile;
        }
        // deal with target conflict
        PathType pathType = PathType.instance(srcFile);
        tarFile = StorageUtil.doConflictName(tarFile, mode, pathType, true);
        long costTime = this.moveFile(srcFile, tarFile);
        log.info("move the file ({} -> {}) completed, cost {}ms", srcFile, tarFile, costTime);
        return tarFile;
    }

    @Override
    public void deleteFile(boolean rf, String fileName, String... superPaths) throws IORuntimeException {
        File file = getFile(fileName, superPaths);
        this.deleteFile(rf, file);
    }

    @Override
    public void deleteFile(boolean rf, File file) throws IORuntimeException {
        log.debug("delete file: {}", file);
        // check source exists
        StorageUtil.exists(file, true);
        if (!FileUtil.isDirEmpty(file) && !rf) {
            log.debug("folder is not empty && rf=false, throw exception.d");
            throw new IORuntimeException("目录不为空：" + file);
        }
        // start time
        long start = SystemClock.now();
        FileUtil.del(file);
        // end time
        long costTime = SystemClock.now() - start;
        log.info("delete file ({}) completed, cost {}ms", file, costTime);
    }

    @Override
    public void downloadFile(OutputStream os, long skipBytes, String fileName, String... superPaths) throws IORuntimeException {
        File file = this.getFile(fileName, superPaths);
        if (!FileUtil.isFile(file))
            log.info("download file: {}", file);
        // check source exists
        StorageUtil.exists(file, true);
        // check skip
        long length = file.length();
        if (skipBytes >= length)
            return;
        try (BufferedInputStream is = FileUtil.getInputStream(file)) {
            // start time
            long start = SystemClock.now();
            // download
            skipBytes = skipBytes < 0 ? 0 : skipBytes;
            int as = 0;
            do {
                as += is.skip(skipBytes - as);
            } while (as < skipBytes);
            IoUtil.copy(is, os);
            // end time
            long costTime = SystemClock.now() - start;
            log.info("download file ({}) completed, cost {}ms", file, costTime);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    private long saveFile(InputStream in, File tarFile, long fileLength) {
        // 计时开始
        long start = SystemClock.now();
        try (OutputStream out = FileUtil.getOutputStream(tarFile)) {
            byte[] buffer = new byte[IoConsts.DEFAULT_BUFFER_SIZE];
            long size = 0;
            for (int readSize; (readSize = in.read(buffer)) != IoConsts.EOF; ) {
                // 指定读取长度
                if (fileLength > 0) {
                    long remainSize = fileLength - size;
                    if (remainSize <= 0)
                        break;
                    if (remainSize < readSize)
                        readSize = (int) remainSize;
                }
                out.write(buffer, 0, readSize);
                size += readSize;
                out.flush();
            }
        } catch (IORuntimeException e) {
            StorageUtil.silentDelete(tarFile);
            throw e;
        } catch (Exception e) {
            StorageUtil.silentDelete(tarFile);
            throw new IORuntimeException(e);
        }
        // 计时结束
        return SystemClock.now() - start;
    }

    private long copyFile(File srcFile, File tarFile) {
        // 源与目标同文件检查
        if (FileUtil.equals(srcFile, tarFile)) {
            log.debug("save file: Files '{}' and '{}' are equal", srcFile, tarFile);
            return 0;
        }
        // 计时开始
        long start = SystemClock.now();
        // 保存
        try {
            FileUtil.copyFile(srcFile, tarFile, StandardCopyOption.REPLACE_EXISTING);
        } catch (IORuntimeException e) {
            StorageUtil.silentDelete(tarFile);
            throw e;
        } catch (Exception e) {
            StorageUtil.silentDelete(tarFile);
            throw new IORuntimeException(e);
        }
        // 计时结束
        return SystemClock.now() - start;
    }

    private long saveFile(byte[] bytes, File tarFile) {
        // 计时开始
        long start = SystemClock.now();
        // 保存
        try {
            FileUtil.writeBytes(bytes, tarFile);
        } catch (IORuntimeException e) {
            StorageUtil.silentDelete(tarFile);
            throw e;
        } catch (Exception e) {
            StorageUtil.silentDelete(tarFile);
            throw new IORuntimeException(e);
        }
        // 计时结束
        return SystemClock.now() - start;
    }

    private long renameFile(File srcFile, String newName) {
        File tarFile = FileUtil.file(srcFile.getParentFile(), newName);
        // 源与目标同文件检查
        if (FileUtil.equals(srcFile, tarFile)) {
            log.debug("rename file: Files '{}' and '{}' are equal", srcFile, tarFile);
            return 0;
        }
        // rename
        // 计时开始
        long start = SystemClock.now();
        try {
            FileUtil.rename(srcFile, newName, true);
        } catch (IORuntimeException e) {
            StorageUtil.silentDelete(tarFile);
            throw e;
        } catch (Exception e) {
            StorageUtil.silentDelete(tarFile);
            throw new IORuntimeException(e);
        }
        // 计时结束
        return SystemClock.now() - start;
    }

    private long moveFile(File srcFile, File tarFile) {
        // source equals target
        if (FileUtil.equals(srcFile, tarFile)) {
            log.debug("move file: Files '{}' and '{}' are equal", srcFile, tarFile);
            return 0;
        }
        // start time
        long start = SystemClock.now();
        // move
        try {
            FileUtil.move(srcFile, tarFile, true);
        } catch (IORuntimeException e) {
            StorageUtil.silentDelete(tarFile);
            throw e;
        } catch (Exception e) {
            StorageUtil.silentDelete(tarFile);
            throw new IORuntimeException(e);
        }
        // end time
        return SystemClock.now() - start;
    }


}
